package com.alisls.activiti.service.service.impl;

import com.alisls.activiti.service.req.ModelREQ;
import com.alisls.activiti.service.service.ModelService;
import com.alisls.activiti.service.vo.ModelVO;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ModelQuery;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 模型服务
 *
 * @author Ke Wang
 */
@Service
@Slf4j
public class ModelServiceImpl extends ActivitiServiceImpl implements ModelService {

    @Resource
    private RepositoryService repositoryService;


    @Override
    public String save(ModelVO modelVO) throws Exception {
        Model model = repositoryService.newModel();
        model.setKey(modelVO.getKey());
        model.setName(modelVO.getName());
        model.setVersion(0);

        String metaInfo = generateMetaInfo(modelVO);
        model.setMetaInfo(metaInfo);

        // 保存模型到数据表act_re_model
        repositoryService.saveModel(model);

        byte[] editorSource = generateEditorSource(model.getKey());

        // 保存模型EditorSource
        repositoryService.addModelEditorSource(model.getId(), editorSource);
        return null;
    }

    /**
     * 根据模型标识删除模型
     * @param id 模型标识
     * @return 是否删除成功
     */
    @Override
    public Boolean delete(String id) {
        repositoryService.deleteModel(id);
        return Boolean.TRUE;
    }

    /**
     * 分页查询模型
     * @param modelREQ 查询条件
     * @return 查询结果
     */
    @Override
    public Map<String, Object> page(ModelREQ modelREQ) {
        ModelQuery modelQuery = repositoryService.createModelQuery()
                .orderByCreateTime()
                .desc();

        if (StringUtils.hasLength(modelREQ.getName())) {
            modelQuery.modelNameLike("%" + modelREQ.getName() + "%");
        }
        if (StringUtils.hasLength(modelREQ.getKey())) {
            modelQuery.modelKey(modelREQ.getKey());
        }

        List<Model> modelList = modelQuery.listPage(modelREQ.getFirstResult(), modelREQ.getSize());
        Long total = modelQuery.count();

        List<ModelVO> modelVOs = new ArrayList<>();
        for (Model model: modelList) {
            ModelVO modelVO = new ModelVO();
            BeanUtils.copyProperties(model, modelVO);
            modelVOs.add(modelVO);
        }

        Map<String, Object> result = new HashMap<>();
        result.put("records", modelVOs);
        result.put("total", total);
        return result;
    }

    /**
     * 通过模型数据部署流程定义
     * @param modelId 模型标识
     * @return 结果
     * @throws Exception 异常
     */
    @Override
    public Boolean deploy(String modelId) throws Exception {
        // 1. 查询流程模型的JSON的字节码
        byte[] jsonBytes = repositoryService.getModelEditorSource(modelId);
        if (jsonBytes == null) {
            throw new Exception("请先设计流程模型");
        }

        // 2. 将JSON转化为XML（bpmn2.0中规范流程模型描述是XML格式）
        byte[] xmlBytes = this.bpmnByteJsonToXml(jsonBytes);
        if (xmlBytes == null) {
            throw new Exception("请先设计流程模型");
        }

        // 3. 查询流程定义的图片
        byte[] pngBytes = repositoryService.getModelEditorSourceExtra(modelId);

        Model model = repositoryService.getModel(modelId);
        if (model == null) {
            throw new Exception("流程模型不存在！");
        }

        // Xml资源名称，对应act_ge_bytearray表中的name_字段
        String xmlName = model.getName() + ".bpmn20.xml";
        // 图片资源名称，对应act_ge_bytearray表中的name_字段
        String pngName = model.getName() + "." + model.getKey() + ".png";

        // 4. 调用API部署
        Deployment deploy = repositoryService.createDeployment()
                // 部署名称
                .name(model.getName())
                // Xml资源
                .addString(xmlName, new String(xmlBytes, StandardCharsets.UTF_8))
                // Png资源
                .addBytes(pngName, pngBytes)
                .deploy();

        // 5. 更新部署ID到流程模型表DEPLOYMENT_ID字段
        model.setDeploymentId(deploy.getId());
        repositoryService.saveModel(model);

        return Boolean.TRUE;
    }

    @Override
    public void exportZip(String modelId, HttpServletResponse response) {
    }

    /**
     * 生成Model字段meta数据
     * @param modelVO 模型视图
     * @return Meta字段的JSON数据
     */
    private String generateMetaInfo(ModelVO modelVO) throws JsonProcessingException {
        // 封装模型的JSON字符串
        ObjectNode objectNode = objectMapper.createObjectNode();
        objectNode.put(ModelDataJsonConstants.MODEL_NAME, modelVO.getName());
        objectNode.put(ModelDataJsonConstants.MODEL_REVISION, modelVO.getVersion());
        objectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, modelVO.getDescription());
        return objectMapper.writeValueAsString(objectNode);
    }

    /**
     * 生成模型对象基础信息
     * @return 模型对象基础信息的JSON数据
     */
    private byte[] generateEditorSource(String key) {
        // 封装模型对象基础信息
        /*
        {
            "id": "canvas",
            "resourceId": "canvas",
            "stencilset": {"namespace": "http://b3mn.org/stencilset/bpmn2.0#"},
            "properties": {"process_id": "未定义"}
        }
        */

        ObjectNode editorNode = objectMapper.createObjectNode();

        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace", "http://b3mn.org/stenciset/bpmn2.0#");
        editorNode.replace("stencilset", stencilSetNode);

        ObjectNode propertiesNode = objectMapper.createObjectNode();
        propertiesNode.put("process_id", key);
        editorNode.replace("properties", propertiesNode);

        return editorNode.toString().getBytes(StandardCharsets.UTF_8);
    }

    /**
     * 将bpmn数据JSON字节码格式转化为XML字节码格式
     * @param jsonBytes JSON字节码
     * @return XML字节码
     */
    private byte[] bpmnByteJsonToXml(byte[] jsonBytes) throws IOException {
        // 1. Json字节码转成BpmnModel对象
        JsonNode jsonNode = objectMapper.readTree(jsonBytes);
        BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(jsonNode);
        if (bpmnModel.getProcesses().size() == 0) {
            return null;
        }

        // 2. BpmnModel对象转为XML字节码
        byte[] xmlBytes = new BpmnXMLConverter().convertToXML(bpmnModel);
        return xmlBytes;
    }

}
